iT邦幫忙

2024 iThome 鐵人賽

DAY 4
1
Software Development

用 NestJS 闖蕩微服務!系列 第 4

[用NestJS闖蕩微服務!] DAY04 - Redis Transporter

  • 分享至 

  • xImage
  •  

什麼是 Redis ?

Redis Logo

圖片來源

Redis 是一個基於記憶體的 key value 資料庫,在過去最常用來當作 快取(Cache) 的媒介,隨著 Redis 日漸茁壯,開始提供持久性的功能,使它也能夠用來當作 文件資料庫(Document Dadabase),甚至還提供了 消息代理(Message Broker) 的功能,可說是非常強大的工具。

安裝 Redis

在終端機輸入下方指令以便從 Docker Hub 下載 Redis 的 Docker Image:

$ docker pull redis

下載後,透過下方指令將 Redis 架設在 6379 port:

$ docker run --name <NAME> -p 6379:6379 -d redis

透過 Container 名稱即可對該 Container 下指令,進而使用 Redis CLI 來操作 Redis。透過下方指令執行 Container 內的 bash:

$ docker exec -it <NAME> bash

接著,輸入下方指令進入 Redis CLI:

$ redis-cli

如此一來,便可以對 Redis 進行操作。透過下方指令進行測試,會在終端機顯示 Pong

$ Ping 

Redis Ping Pong

Redis Pub/Sub

前面提到 Redis 不僅可以用來當作 Cache 的媒介,還可以用來實作 Message Broker,那該如何實作呢?Redis 在第二版的時候新增 發佈訂閱(Publish/Subscribe) 的機制,簡稱 Redis Pub/Sub,讓服務之間可以透過 Redis 來交換訊息,只要 訂閱方(Subscriber) 訂閱 發佈方(Publisher) 所指定的 通道(Channel),當 Publisher 發送了一則訊息到該通道時,Subscriber 就會收到該訊息,以架構面來說,Subscriber 並不會直接與 Publisher 進行溝通,而是透過 Redis 作為 Message Broker,如此一來便解除了Subscriber 與 Publisher 之間的耦合關係。

Redis Pub/Sub Concept

在讓兩個服務透過該機制進行交換訊息之前,可以先試著用 Redis CLI 來操作,首先,打開兩個終端機並進入 Redis CLI,在其中一個終端機輸入下方指令來訂閱名稱為 test-channel 的通道:

$ SUBSCRIBE test-channel

Redis Basic Subscription

接著,透過另一個終端機輸入下方指令發送訊息到名稱為 test-channel 的通道:

$ PUBLISH test-channel "Hello World"

Redis Basic Publisher

此時 Subscriber 會收到訊息:

Redis Basic Subscription Result

如果要取消訂閱可以使用下方指令:

$ UNSUBSCRIBE test-channel

Redis Pub/Sub 支援透過 Pattern 的方式來接收訊息,透過下方指令訂閱通道名稱符合 order.* 的訊息:

$ PSUBSCRIBE order.*

此時對 order.created 發送訊息:

$ PUBLISH order.created "Hello World"

Subscriber 會順利收到發送的訊息:

Redis Pattern Subscription Result

注意:Redis Pub/Sub 並不會保存已發出的訊息,所以 如果 Subscriber 是在訊息 發佈後 才進行訂閱,那就會收不到該筆訊息,進而造成資料丟失的情況。

Redis Transporter

NestJS 實作了 Redis Transporter,讓微服務應用程式可以用跟其他 Transporter 一樣的開發風格來使用 Redis Pub/Sub,當服務之間的溝通錯綜複雜時,相較於服務間直接溝通會是更好的選擇。

前置作業

要使用 Redis Transporter 之前,需要先安裝下方套件:

$ npm install ioredis

補充ioredis 是一套用於 Node.js 的強大、功能齊全 的 Redis 客戶端,有興趣可以參考官方文件

建立微服務應用程式

修改載入點 main.ts 的內容,將 transport 設定為 Transport.REDIS,並根據架設的 Redis 位址來設定 hostport 的資訊:

import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.REDIS,
      options: {
        host: '0.0.0.0',
        port: 6379,
      },
    },
  );
  await app.listen();
}
bootstrap();

上方範例在 options 只使用了 hostport,事實上,Redis Transporter 有下列五個屬性可以設定:

  • host:要連線的主機,如:localhost
  • port:指定要連線的主機 port,如:3333
  • retryAttempts:當無法連至主機時的重試次數,預設是 0
  • retryDelay:每次重試的間隔時間,以毫秒(ms)為單位,預設是 0
  • wildcards:是否啟用 Redis Pub/Sub 的 Pattern 功能,預設是 false

Transporter 訊息模式

Redis Transporter 也支援 Request-response 與 Event-based 訊息模式。下方是範例程式碼,在 AppController 內實作 sayHello 方法並套用 @MessagePattern 裝飾器,以及實作 onOrderCreated 方法並套用 @EventPattern 裝飾器:

import { Controller } from '@nestjs/common';
import { EventPattern, MessagePattern } from '@nestjs/microservices';

@Controller()
export class AppController {
  @MessagePattern({ cmd: 'hello' })
  sayHello(data: string) {
    console.log(data);
    return `Hello, ${data}`;
  }

  @EventPattern('order.created')
  onOrderCreated(order: { name: string }) {
    console.log(order);
  }
}

使用 Redis CLI 進行測試,透過下方指令將訊息發送到 order.created 通道:

$ PUBLISH order.created "Hello World"

此時在微服務應用程式的終端機會顯示發送的訊息:

NestJS Redis Transporter Test Result1

那如果 Pattern 設定成可序列化物件的話,要如何透過 Redis CLI 發送訊息呢?又或是使用其他技術的微服務應用程式該如何發送訊息呢?很簡單,將該可序列化物件轉成 JSON 字串即可,以上方程式碼來說,我們要將 { cmd: 'hello' } 轉成字串 '{"cmd":"hello"}' 當作通道名稱:

$ PUBLISH '{"cmd":"hello"}' "Hello World"

此時在微服務應用程式的終端機會顯示發送的訊息:

NestJS Redis Transporter Test Result2

取得 Payload 與 Context

假如要取得該請求的相關資訊,比如:通道名稱,可以透過 @Ctx 裝飾器取得 RedisContext。下方是範例程式碼:

import { Controller } from '@nestjs/common';
import {
  Ctx,
  EventPattern,
  MessagePattern,
  Payload,
  RedisContext
} from '@nestjs/microservices';

@Controller()
export class AppController {
  @EventPattern('order.created')
  onOrderCreated(
    @Payload() order: { name: string },
    @Ctx() ctx: RedisContext
  ) {
    console.log(ctx.getChannel());
    console.log(order);
  }
}

使用 Redis CLI 進行測試,透過下方指令將訊息發送到 order.created 通道:

$ PUBLISH order.created "Hello World"

此時在微服務應用程式的終端機會顯示發送的訊息以及通道名稱:

NestJS Redis Transporter Test Context Result

建立客戶端

修改 AppModule 的內容,透過 ClientsModule 建立 Redis Transporter 的 ClientProxy

import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
// ...

@Module({
  // ...
  imports: [
    ClientsModule.register([
      {
        name: 'REDIS_SERVICE',
        transport: Transport.REDIS,
        options: {
          host: '0.0.0.0',
          port: 6379
        }
      }
    ])
  ]
})
export class AppModule {}

傳送訊息

由於 Redis Transporter 支援 Request-response 與 Event-based 訊息模式,所以可以使用 ClientProxysendemit 方法。下方是範例程式碼,修改 AppController 的內容,使用 @Inject 裝飾器注入 ClientProxy,並設計 getHelloonOrderCreated 方法:

import { Inject, Controller, Get } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';

@Controller()
export class AppController {
  constructor(
    @Inject('REDIS_SERVICE') private readonly redisService: ClientProxy
  ) {}

  @Get()
  getHello() {
    return this.redisService.send({ cmd: 'hello' }, 'HAO');
  }

  @Get('orderCreated')
  onOrderCreated() {
    return this.redisService.emit('order.created', { name: 'test' });
  }
}

透過 Postman 使用 GET 方法存取 http://localhost:3000,會看到下方的結果:

NestJS Redis Client Proxy Result1

透過 Postman 使用 GET 方法存取 http://localhost:3000/orderCreated,在微服務應用程式的終端機會看到 { name: 'test' }

NestJS Redis Client Proxy Result2

Request-response 的實現原理

Redis Pub/Sub 是單向傳輸訊息的運作模式,那麼 NestJS 是如何實現 Request-response 訊息模式的呢?NestJS 會讓微服務應用程式訂閱指定通道名稱的訊息,比如:order.created,在回覆訊息時,會向 order.created.reply 通道發送訊息,NestJS 客戶端會訂閱該通道以獲取回應,這也是為什麼前面提到 Request-response 會消耗較多資源的主因。

Request-response in Redis Concept

我們可以透過 Redis CLI 來觀察這個行為,透過下方指令訂閱所有通道的訊息:

$ PSUBSCRIBE *

接著,透過 Postman 使用 GET 方法存取 http://localhost:3000,會在 Redis CLI 上看見訂閱的通道訊息以及對應 reply 通道的訊息:

Request-response Redis reply Result

小結

Redis 是一個強大的工具,經常用來當作 Cache 的媒介,甚至在後面的版本可以用來當作 Document Database 以及 Message Broker,也因為 Redis 提供了 Redis Pub/Sub 功能,讓服務之間可以透過這個功能實現 Publish/Subscribe Pattern,進而消除服務之間的耦合。

NestJS 設計了 Redis Transporter 讓開發者可以用跟其他 Transporter 一樣的開發風格來使用 Redis Pub/Sub。

由於 Redis Pub/Sub 本身是單向傳輸訊息的運作模式,NestJS 在實現 Request-response 訊息模式時,必須額外開啟 reply 通道,這也是為什麼前面提到會消耗較多資源的主因。


上一篇
[用NestJS闖蕩微服務!] DAY03 - 初探微服務應用程式(下)
下一篇
[用NestJS闖蕩微服務!] DAY05 - MQTT Transporter
系列文
用 NestJS 闖蕩微服務!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言